﻿using System;
using System.Collections.Generic;

namespace KstopaCommon
{
    public class CalFormula
    {
        #region 通用
        /// <summary>
        /// 求数组最大值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="array"></param>
        /// <returns></returns>
        public static T GetMaxValue<T>(T[] array)
        {
            if (array.Length == 0)
            {
                return default(T);
            }
            Array.Sort(array);
            return array[array.Length - 1];
        }

        /// <summary>
        /// 求数组最小值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="array"></param>
        /// <returns></returns>
        public static T GetMinValue<T>(T[] array)
        {
            if (array.Length == 0)
            {
                return default(T);
            }
            Array.Sort(array);
            return array[0];
        }

        /// <summary>
        /// 获取字符串中的所有整数
        /// </summary>
        /// <param name="_string"></param>
        /// <returns></returns>
        public static List<int> GetIntegerValuesInString(string _string)
        {
            List<int> ret = new List<int>();
            List<string> integerstrs = new List<string>();
            string numstr = "";
            foreach (char item in _string)
            {
                if (char.IsDigit(item))
                {
                    numstr += item;
                }
                else
                {
                    if (numstr != "")
                    {
                        integerstrs.Add(numstr);
                        numstr = "";
                    }
                }
            }
            foreach (var item in integerstrs)
            {
                ret.Add(Convert.ToInt32(item));
            }
            return ret;
        }

        #endregion

        #region 几何

        /// <summary>
        /// 求一点绕中心旋转角度后的坐标
        /// 逆时针角度取正顺时针取负
        /// </summary>
        /// <param name="startpoint">起始点</param>
        /// <param name="centerpoint">旋转中心</param>
        /// <param name="angle">旋转角度</param>
        /// <returns></returns>
        public static PointD GetNewPointRotatingByAngle(PointD startpoint, PointD centerpoint, double angle)
        {
            PointD endpoint = new PointD(0, 0);
            double _angle = angle * Math.PI / 180;
            endpoint.X = Math.Round((startpoint.X - centerpoint.X) * Math.Cos(_angle) - (startpoint.Y - centerpoint.Y) * Math.Sin(_angle) + centerpoint.X, 4);
            endpoint.Y = Math.Round((startpoint.Y - centerpoint.Y) * Math.Cos(_angle) + (startpoint.X - centerpoint.X) * Math.Sin(_angle) + centerpoint.Y, 4);
            return endpoint;
        }

        /// <summary>
        /// 求过三点的圆心
        /// </summary>
        /// <param name="point0"></param>
        /// <param name="point1"></param>
        /// <param name="point2"></param>
        /// <returns></returns>
        public static PointD GetCircleCenterByThreePoints(PointD point0, PointD point1, PointD point2)
        {
            //PointD centerpoint = new PointD(0, 0);
            //double a = 0.5 * ((point0.X + point1.X) * (point1.X - point0.X) + (point0.Y + point1.Y) * (point1.Y - point0.Y));
            //double b = 0.5 * ((point0.X + point2.X) * (point2.X - point0.X) + (point0.Y + point2.Y) * (point2.Y - point0.Y));
            //centerpoint.X = Math.Round((a * (point2.X - point0.X) - b * (point1.X - point0.X)) / ((point2.X - point0.X) * (point1.Y - point0.Y) - (point2.Y - point0.Y) * (point1.X - point0.X)), 4);
            //centerpoint.Y = Math.Round((b - point0.Y * (point2.Y - point0.Y)) / (point2.X - point0.X), 4);
            //return centerpoint;

            PointD centerpoint = new PointD(0, 0);
            double x1, y1, x2, y2, x3, y3;
            double a, b, c, g, e, f;
            x1 = point0.X;
            y1 = point0.Y;
            x2 = point1.X;
            y2 = point1.Y;
            x3 = point2.X;
            y3 = point2.Y;
            e = 2 * (x2 - x1);
            f = 2 * (y2 - y1);
            g = x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1;
            a = 2 * (x3 - x2);
            b = 2 * (y3 - y2);
            c = x3 * x3 - x2 * x2 + y3 * y3 - y2 * y2;
            centerpoint.X = Math.Round((g * b - c * f) / (e * b - a * f), 6);
            centerpoint.Y = Math.Round((a * g - c * e) / (a * f - b * e), 6);
            double R = Math.Sqrt((centerpoint.X - x1) * (centerpoint.X - x1) + (centerpoint.Y - y1) * (centerpoint.Y - y1));
            return centerpoint;
        }

        /// <summary>
        /// 求两直线交点
        /// </summary>
        /// <param name="lineFirst">直线1的中心点和角度</param>
        /// <param name="lineSecond">直线2的中心点和角度</param>
        /// <returns>交点，返回(0,0)为平行线</returns>
        public static PointD GetIntersectionByTwoLines(PointD lineFirst, PointD lineSecond)
        {
            PointD crosspoint = new PointD(0, 0);
            bool enable1, enable2;
            double k1 = 0, k2 = 0;
            if (lineFirst.Angle != 90)
            {
                k1 = Math.Tan(lineFirst.Angle / 180 * Math.PI);
                enable1 = true;
            }
            else
            {
                enable1 = false;
            }
            if (lineSecond.Angle != 90)
            {
                k2 = Math.Tan(lineSecond.Angle / 180 * Math.PI);
                enable2 = true;
            }
            else
            {
                enable2 = false;
            }

            if (k1 == k2)
            {
                return crosspoint;
            }

            if (enable1 && enable2)
            {
                crosspoint.X = ((lineSecond.Y - lineFirst.Y) + (k1 * lineFirst.X - k2 * lineSecond.X)) / (k1 - k2);
                crosspoint.Y = k1 * crosspoint.X + lineFirst.Y - k1 * lineFirst.X;
            }
            else if (enable1 == false && enable2)
            {
                crosspoint.X = lineFirst.X;
                crosspoint.Y = k2 * lineFirst.X + lineSecond.Y - k2 * lineSecond.X;
            }
            else if (enable1 && enable2 == false)
            {
                crosspoint.X = lineSecond.X;
                crosspoint.Y = k1 * lineSecond.X + lineFirst.Y - k1 * lineFirst.X;
            }
            return crosspoint;
        }

        /// <summary>
        /// 求两直线交点
        /// </summary>
        /// <param name="lineFirstStart">直线1起点</param>
        /// <param name="lineFirstEnd">直线1终点</param>
        /// <param name="lineSecondStart">直线2起点</param>
        /// <param name="lineSecondEnd">直线2终点</param>
        /// <returns></returns>
        public static PointD GetIntersectionByTwoLines(PointD lineFirstStart, PointD lineFirstEnd, PointD lineSecondStart, PointD lineSecondEnd)
        {
            /*
             * L1，L2都存在斜率的情况：
             * 直线方程L1: ( y - y1 ) / ( y2 - y1 ) = ( x - x1 ) / ( x2 - x1 ) 
             * => y = [ ( y2 - y1 ) / ( x2 - x1 ) ]( x - x1 ) + y1
             * 令 a = ( y2 - y1 ) / ( x2 - x1 )
             * 有 y = a * x - a * x1 + y1   .........1
             * 直线方程L2: ( y - y3 ) / ( y4 - y3 ) = ( x - x3 ) / ( x4 - x3 )
             * 令 b = ( y4 - y3 ) / ( x4 - x3 )
             * 有 y = b * x - b * x3 + y3 ..........2
             * 
             * 如果 a = b，则两直线平等，否则， 联解方程 1,2，得:
             * x = ( a * x1 - b * x3 - y1 + y3 ) / ( a - b )
             * y = a * x - a * x1 + y1
             * 
             * L1存在斜率, L2平行Y轴的情况：
             * x = x3
             * y = a * x3 - a * x1 + y1
             * 
             * L1 平行Y轴，L2存在斜率的情况：
             * x = x1
             * y = b * x - b * x3 + y3
             * 
             * L1与L2都平行Y轴的情况：
             * 如果 x1 = x3，那么L1与L2重合，否则平等
             * 
            */
            double a = 0, b = 0;
            int state = 0;
            if (lineFirstStart.X != lineFirstEnd.X)
            {
                a = (lineFirstEnd.Y - lineFirstStart.Y) / (lineFirstEnd.X - lineFirstStart.X);
                state |= 1;
            }
            if (lineSecondStart.X != lineSecondEnd.X)
            {
                b = (lineSecondEnd.Y - lineSecondStart.Y) / (lineSecondEnd.X - lineSecondStart.X);
                state |= 2;
            }
            switch (state)
            {
                case 0: //L1与L2都平行Y轴
                    {
                        if (lineFirstStart.X == lineSecondStart.X)
                        {
                            //throw new Exception("两条直线互相重合，且平行于Y轴，无法计算交点。");
                            return new PointD(0, 0);
                        }
                        else
                        {
                            //throw new Exception("两条直线互相平行，且平行于Y轴，无法计算交点。");
                            return new PointD(0, 0);
                        }
                    }
                case 1: //L1存在斜率, L2平行Y轴
                    {
                        double x = lineSecondStart.X;
                        double y = (lineFirstStart.X - x) * (-a) + lineFirstStart.Y;
                        return new PointD(x, y);
                    }
                case 2: //L1 平行Y轴，L2存在斜率
                    {
                        double x = lineFirstStart.X;
                        //网上有相似代码的，这一处是错误的。你可以对比case 1 的逻辑 进行分析
                        //源code:lineSecondStart * x + lineSecondStart * lineSecondStart.X + p3.Y;
                        double y = (lineSecondStart.X - x) * (-b) + lineSecondStart.Y;
                        return new PointD(x, y);
                    }
                case 3: //L1，L2都存在斜率
                    {
                        if (a == b)
                        {
                            // throw new Exception("两条直线平行或重合，无法计算交点。");
                            return new PointD(0, 0);
                        }
                        double x = (a * lineFirstStart.X - b * lineSecondStart.X - lineFirstStart.Y + lineSecondStart.Y) / (a - b);
                        double y = a * x - a * lineFirstStart.X + lineFirstStart.Y;
                        return new PointD(x, y);
                    }
            }
            // throw new Exception("不可能发生的情况");
            return new PointD(0, 0);
        }

        /// <summary>
        /// 求两点间距离
        /// </summary>
        /// <param name="A"></param>
        /// <param name="B"></param>
        /// <returns></returns>
        public static double GetDistanceOfTwoPoints(PointD A, PointD B)
        {
            return Math.Sqrt((A.X - B.X) * (A.X - B.X) + (A.Y - B.Y) * (A.Y - B.Y));
        }

        /// <summary>
        /// 求一点关于中心的对称点
        /// </summary>
        /// <param name="A"></param>
        /// <param name="centerPoint"></param>
        /// <returns></returns>
        public static PointD GetSymmetryOfPointA(PointD A, PointD centerPoint)
        {
            return new PointD(2 * centerPoint.X - A.X, 2 * centerPoint.Y - A.Y);
        }

        /// <summary>
        /// 求点P到线段AB的最短距离
        /// </summary>
        /// <param name="A">线段A端</param>
        /// <param name="B">线段B端</param>
        /// <param name="P">点P</param>
        /// <returns></returns>
        public static double GetDistanceFromPointToSegmentLine(PointD A, PointD B, PointD P)
        {
            double AB = GetDistanceOfTwoPoints(A, B);
            double AP = GetDistanceOfTwoPoints(A, P);
            double r = ((P.X - A.X) * (B.X - A.X) + (P.Y - A.Y) * (B.Y - A.Y)) / (AB * AB);//AC'=r*AB'
            if (r <= 0)
            {
                return AP;
            }
            else if (r >= 1)
            {
                return GetDistanceOfTwoPoints(B, P);
            }
            else
            {
                double AC = r * AB;
                return Math.Sqrt(AP * AP - AC * AC);
            }
        }

        /// <summary>
        /// 求点P到直线AB的最短距离
        /// </summary>
        /// <param name="A">直线一点A</param>
        /// <param name="B">直线一点B</param>
        /// <param name="P">点P</param>
        /// <returns></returns>
        public static double GetDistanceFromPointToLine(PointD A, PointD B, PointD P)
        {
            double AB = GetDistanceOfTwoPoints(A, B);
            double AP = GetDistanceOfTwoPoints(A, P);
            double r = ((P.X - A.X) * (B.X - A.X) + (P.Y - A.Y) * (B.Y - A.Y)) / (AB * AB);//AC'=r*AB'
            double AC = r * AB;
            return Math.Sqrt(AP * AP - AC * AC);
        }

        /// <summary>
        /// 求线段1与线段2间的最短距离
        /// </summary>
        /// <param name="lineFirstStart"></param>
        /// <param name="lineFirstEnd"></param>
        /// <param name="lineSecondStart"></param>
        /// <param name="lineSecondEnd"></param>
        /// <returns></returns>
        public static double GetDistanceBetweenTwoSegmentLines(PointD lineFirstStart, PointD lineFirstEnd, PointD lineSecondStart, PointD lineSecondEnd)
        {
            double distance = 0;
            if (GetDirBetweenPointAndLine(lineFirstStart, lineFirstEnd, lineSecondStart) * GetDirBetweenPointAndLine(lineFirstStart, lineFirstEnd, lineSecondEnd) <= 0
                && GetDirBetweenPointAndLine(lineSecondStart, lineSecondEnd, lineFirstStart) * GetDirBetweenPointAndLine(lineSecondStart, lineSecondEnd, lineFirstEnd) <= 0)
            {
                distance = 0;
            }
            else
            {
                bool hasvalve = false;
                double[] _array = new double[4]{GetDistanceFromPointToSegmentLine(lineFirstStart, lineFirstEnd, lineSecondStart), GetDistanceFromPointToSegmentLine(lineFirstStart, lineFirstEnd, lineSecondEnd)
                    , GetDistanceFromPointToSegmentLine(lineSecondStart, lineSecondEnd, lineFirstStart), GetDistanceFromPointToSegmentLine(lineSecondStart, lineSecondEnd, lineFirstEnd) };
                foreach (double item in _array)
                {
                    if (hasvalve)
                    {
                        if (item < distance)
                        {
                            distance = item;
                        }
                    }
                    else
                    {
                        distance = item;
                        hasvalve = true;
                    }
                }
            }
            return distance;
        }

        /// <summary>
        /// 已知线段AB,求反向延长线上一点，距A点长度len
        /// </summary>
        /// <param name="A"></param>
        /// <param name="B"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static PointD GetPointOfReverseExtensionLine(PointD A, PointD B, double len)
        {
            PointD point = new PointD();
            if (A.X - B.X == 0)
            {
                point.X = A.X;
                if (A.Y - B.Y > 0)
                {
                    point.Y = A.Y + len;
                }
                else
                {
                    point.Y = A.Y - len;
                }
            }
            else if (A.Y - B.Y == 0)
            {
                point.Y = A.Y;
                if (A.X - B.X > 0)
                {
                    point.X = A.X + len;
                }
                else
                {
                    point.X = A.X - len;
                }
            }
            else
            {
                double k = (A.Y - B.Y) / (A.X - B.X);
                double b = A.Y - k * A.X;
                double zoom = len / (Math.Sqrt((A.X - B.X) * (A.X - B.X) + (A.Y - B.Y) * (A.Y - B.Y)));
                if (k > 0)
                {
                    if (A.X - B.X > 0)
                    {
                        point.X = A.X + zoom * (A.X - B.X);
                        point.Y = k * point.X + b;
                    }
                    else
                    {
                        point.X = A.X - zoom * (B.X - A.X);
                        point.Y = k * point.X + b;
                    }
                }
                else
                {
                    if (A.X - B.X > 0)
                    {
                        point.X = A.X + zoom * (A.X - B.X);
                        point.Y = k * point.X + b;
                    }
                    else
                    {
                        point.X = A.X - zoom * (B.X - A.X);
                        point.Y = k * point.X + b;
                    }
                }
            }
            return point;
        }

        /// <summary>
        /// 已知线段AB,求延长线上一点，距B点长度len
        /// </summary>
        /// <param name="A"></param>
        /// <param name="B"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static PointD GetPointOfExtensionLine(PointD A, PointD B, double len)
        {
            PointD point = new PointD();
            if (A.X - B.X == 0)
            {
                point.X = A.X;
                if (A.Y - B.Y > 0)
                {
                    point.Y = B.Y - len;
                }
                else
                {
                    point.Y = B.Y + len;
                }
            }
            else if (A.Y - B.Y == 0)
            {
                point.Y = A.Y;
                if (A.X - B.X > 0)
                {
                    point.X = B.X - len;
                }
                else
                {
                    point.X = B.X + len;
                }
            }
            else
            {
                double k = (A.Y - B.Y) / (A.X - B.X);
                double b = A.Y - k * A.X;
                double zoom = len / (Math.Sqrt((A.X - B.X) * (A.X - B.X) + (A.Y - B.Y) * (A.Y - B.Y)));

                point.X = B.X + zoom * (B.X - A.X);
                point.Y = k * point.X + b;
            }
            return point;
        }

        /// <summary>
        /// 在图像坐标系中，已知线段AB，求过端点A的垂直线上一点，且距A长度为len
        /// </summary>
        /// <param name="A">起点A</param>
        /// <param name="B">终点B</param>
        /// <param name="len">距离</param>
        /// <param name="cw">旋转方向，默认为顺时针方向</param>
        /// <returns></returns>
        public static PointD GetPointOfVerticalLineInImageCoordinateSystem(PointD A, PointD B, double len, bool cw = true)
        {
            PointD point = new PointD();
            int cwValue = cw == true ? 1 : -1;
            if (A.X - B.X == 0)
            {
                point.Y = A.Y;
                if (A.Y - B.Y > 0)
                {
                    point.X = A.X + len * cwValue;
                }
                else
                {
                    point.X = B.X - len * cwValue;
                }
            }
            else if (A.Y - B.Y == 0)
            {
                point.X = A.X;
                if (A.X - B.X > 0)
                {
                    point.Y = A.Y - len * cwValue;
                }
                else
                {
                    point.Y = B.Y + len * cwValue;
                }
            }
            else
            {
                double k = (A.Y - B.Y) / (A.X - B.X);
                double _k = (-1) / k;
                double angle = Math.Atan(_k);

                if (k > 0)
                {
                    if (A.Y - B.Y > 0)
                    {
                        point.X = A.X + len * Math.Cos(angle) * cwValue;
                        point.Y = A.Y + len * Math.Sin(angle) * cwValue;
                    }
                    else
                    {
                        point.X = A.X - len * Math.Cos(angle) * cwValue;
                        point.Y = A.Y - len * Math.Sin(angle) * cwValue;
                    }
                }
                else
                {
                    if (A.Y - B.Y > 0)
                    {
                        point.X = A.X + len * Math.Cos(angle) * cwValue;
                        point.Y = A.Y + len * Math.Sin(angle) * cwValue;
                    }
                    else
                    {
                        point.X = A.X - len * Math.Cos(angle) * cwValue;
                        point.Y = A.Y - len * Math.Sin(angle) * cwValue;
                    }
                }
            }
            return point;
        }

        /// <summary>
        /// 已知圆弧的圆心和起点，求起点切线上距起点长len的一点（反向）
        /// </summary>
        /// <param name="center">圆心</param>
        /// <param name="tangent">起点</param>
        /// <param name="len">长度</param>
        /// <param name="cw">是否为顺时针</param>
        /// <returns></returns>
        public static PointD GetPointOfArcTangent(PointD center, PointD tangent, double len, bool cw = true)
        {
            PointD point = new PointD();
            int dir = cw == true ? 1 : -1;
            if (center.X - tangent.X == 0)
            {
                point.Y = tangent.Y;
                if (tangent.Y > center.Y)
                {
                    point.X = tangent.X - len * dir;
                }
                else
                {
                    point.X = tangent.X + len * dir;
                }
            }
            else if (center.Y - tangent.Y == 0)
            {
                point.X = tangent.X;
                if (tangent.X > center.X)
                {
                    point.Y = tangent.Y + len * dir;
                }
                else
                {
                    point.Y = tangent.Y - len * dir;
                }
            }
            else
            {
                double k = -(tangent.X - center.X) / (tangent.Y - center.Y);
                double angle = Math.Atan(k);
                if (k > 0)
                {
                    if (tangent.Y > center.Y)
                    {
                        point.X = tangent.X - dir * len * Math.Cos(angle);
                        point.Y = tangent.Y - dir * len * Math.Sin(angle);
                    }
                    else
                    {
                        point.X = tangent.X + dir * len * Math.Cos(angle);
                        point.Y = tangent.Y + dir * len * Math.Sin(angle);
                    }
                }
                else
                {
                    if (tangent.Y > center.Y)
                    {
                        point.X = tangent.X + dir * len * Math.Cos(angle);
                        point.Y = tangent.Y + dir * len * Math.Sin(angle);
                    }
                    else
                    {
                        point.X = tangent.X - dir * len * Math.Cos(angle);
                        point.Y = tangent.Y - dir * len * Math.Sin(angle);
                    }
                }
            }
            return point;
        }

        /// <summary>
        /// 求向量AB与向量AP的外积模
        /// </summary>
        /// <param name="A"></param>
        /// <param name="B"></param>
        /// <param name="P"></param>
        /// <returns></returns>
        private static double GetModulusOfCrossProduct(PointD A, PointD B, PointD P)
        {
            PointD AB = new PointD(B.X - A.X, B.Y - A.Y);
            PointD AP = new PointD(P.X - A.X, P.Y - A.Y);
            return AB.X * AP.Y - AB.Y * AP.X;
        }

        /// <summary>
        /// 求向量AB与向量AP的内积模
        /// </summary>
        /// <param name="A"></param>
        /// <param name="B"></param>
        /// <param name="P"></param>
        /// <returns></returns>
        private static double GetModulusOfDotProduct(PointD A, PointD B, PointD P)
        {
            PointD AB = new PointD(B.X - A.X, B.Y - A.Y);
            PointD AP = new PointD(P.X - A.X, P.Y - A.Y);
            return AB.X * AP.X + AB.X * AP.Y;
        }

        /// <summary>
        /// 点P与线段AB的位置关系
        /// </summary>
        /// <param name="A">线段A端</param>
        /// <param name="B">线段B端</param>
        /// <param name="P">点P</param>
        /// <returns>-1逆时针，1顺时针，-2反向延长线，2延长线，0在线上</returns>
        private static int GetDirBetweenPointAndLine(PointD A, PointD B, PointD P)
        {
            if (GetModulusOfCrossProduct(A, B, P) < 0)
            {
                return -1;//顺时针方向
            }
            else if (GetModulusOfCrossProduct(A, B, P) > 0)
            {
                return 1;//逆时针方向
            }
            else
            {
                if (GetModulusOfDotProduct(A, B, P) < 0)
                {
                    return -2;//反向延长线上
                }
                else
                {
                    if (GetDistanceOfTwoPoints(A, B) >= GetDistanceOfTwoPoints(A, P))
                    {
                        return 0;//在线上
                    }
                    else
                    {
                        return 2;//延长线上
                    }
                }
            }
        }

        #endregion

    }

    public struct PointD
    {
        private double x;

        public double X
        {
            get { return x; }
            set { x = value; }
        }

        private double y;

        public double Y
        {
            get { return y; }
            set { y = value; }
        }

        private double angle;

        public double Angle
        {
            get { return angle; }
            set { angle = value; }
        }

        private double area;

        public double Area
        {
            get { return area; }
            set { area = value; }
        }


        public PointD(double _x, double _y)
        {
            this.x = _x;
            this.y = _y;
            this.angle = 0;
            this.area = 0;
        }

        public PointD(double _x, double _y, double _angle)
        {
            this.x = _x;
            this.y = _y;
            this.angle = _angle;
            this.area = 0;
        }

        public PointD(double x, double y, double area, double angle = 0)
        {
            this.x = Math.Round(x, 3);
            this.y = Math.Round(y, 3);
            this.area = Math.Round(area, 3);
            this.angle = 0;
        }

    }
}

